¿Para qué incluir interactividad?

Durante el proceso de análisis de datos, uno de los recursos más útiles que nos brinda la programación es la capacidad de “iterar” a gran velocidad: escribir código que se encargue de tareas que serían tediosas de resolver “a mano”, y una vez aplicado para un caso en particular volver a usarlo en otros contextos con mínimo esfuerzo adicional. De este modo podemos evaluar distintos aspectos de nuestros datos (identificar valores extremos, calcular métricas agregadas por categoría, realizar modelos estadísticos, obtener gráficos que muestren relaciones, etc) sin que importe tanto si son decenas o millones de observaciones, o si lidiamos con un dataset o con veinte. La gracia de usar código es que permite una automatización de tareas que multiplica nuestra capacidad de análisis, y además nos evita el fastidio de tener que realizar tareas repetitivas… ¡que se encargue la computadora!.

Como si eso fuera poco, existe otro recurso que también permite analizar grandes cantidades de información y sacar conclusiones en forma rápida, que habilita la programación: la visualización interactiva. Así como hemos aprendido a realizar gráficos estáticos, aprendiendo algunos trucos adicionales podremos generar versiones dinámicas, que permiten a la audiencia interactuar con los datos para revisar sus atributos, cambiar las variables comparadas, o “hacer zoom” en distintas áreas para mostrar subconjuntos mas pequeño o más grande de los datos disponibles.

La visualización interactiva puede combinarse con buenos gestos de manos para máximo impacto

Una buena visualización interactiva nos permite “interrogar” a los datos de forma intuitiva, explorándolos de acuerdo a las preguntas que nos surgen al verlos en pantalla. Organizar y reorganizar los datos de forma visual puede ayudar a descubrir patrones y relaciones clave que serían difíciles de discernir al verlos en una tabla o incluso en una visualización estática.

También puede entusiasmarnos compartir una visualización interactiva para hacer accesible ésta capacidad de análisis rápido a un público amplio, invitado a jugar con los datos por su cuenta. Aquí vale aclarar que la audiencia casual rara vez siente ganas de dedicar su tiempo a mover diales para explorar los datos que presentamos. En general una visualización estática, que vaya directo al grano y muestre alguna conclusión, es preferible a una opción interactiva. A pesar de bajar la barrera de entrada al análisis rápido de datos, la interactividad podría ser mucho mas útil para analistas con experiencia que para su audiencia… al menos por ahora.

Interactividad con bajo esfuerzo

Uno de los fuertes de R es sin dudas la calidad y cantidad de herramientas de visualización disponibles. Además de las funciones incluidas en el lenguaje, y de la “gramática” para realizar todo tipo de gráficos que ofrece ggplot2, podemos agregar a nuestro repertorio paquetes adicionales que realizan visualizaciones específicas. En la vertiente de interactividad, son notables los paquetes reunidos bajo el nombre de htmlwidgets, que generan visualizaciones dinámicas con una gran variedad de estilos y recursos.

Paquetes para la visualización interactiva

La particularidad de los htmlwidgets (palabra que yo traduciría como “cositos HTML”) es que traen a R excelentes funciones de visualización desarrolladas en otro idioma: JavaScript. El gran fuerte de JavaScript es la generación de contenido interactivo para sitios web. Los paquetes reunidos en la colección htmlwidgets “envuelven” el código Javascript en instrucciones en R, haciendo un puente entre los dos mundos, y permitiendo generar desde R visualizaciones que pueden publicarse como contenido web. Para nuestros fines, vamos a concentrarnos en una de las opciones en particular: Plotly. Esto debido a que Plotly permite convertir nuestros gráficos realizados con ggplot2 en versiones interactivas, con solo agregar una línea de código. Dicho de otra forma: podemos producir visualizaciones interactivas… ¡sin necesitar nada más que lo que ya hemos aprendido!.

Vamos a demostrarlo con un ejemplo.

Traigamos los datos de Gapminder que ya hemos usado antes:

head(gapminder)
         pais continente  año expVida     pobl    PBIpc
1 Afghanistan       Asia 1952  28.801  8425333 779.4453
2 Afghanistan       Asia 1957  30.332  9240934 820.8530
3 Afghanistan       Asia 1962  31.997 10267083 853.1007
4 Afghanistan       Asia 1967  34.020 11537966 836.1971
5 Afghanistan       Asia 1972  36.088 13079460 739.9811
6 Afghanistan       Asia 1977  38.438 14880372 786.1134

Los paquetes que solemos usar:

library(ggplot2)
library(dplyr)

Y recordemos el código que habíamos usado para crear una visualización como las de Hans Rosling:

color_continentes <- c("Europe" = "darkorange", "Asia" = "red", "Africa" = "blue",
                       "Americas" = "yellow", "Oceania" = "purple")

ggplot(filter(gapminder, año == 2007),
       aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
  geom_point() +
  scale_x_log10() +
  scale_colour_manual(values = color_continentes) + 
  guides(size = "none") +
  theme_minimal() +
  labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
       size = "población (millones)", 
       x = "PBI per capita (USD)", y = "expectativa de vida en años",
       caption = "fuente: Gapminder, www.gapminder.com")

Para realizar una versión interactiva del mismo gráfico, activamos el paquete plotly

library(plotly)

Ahora solo necesitamos dos cosas:

  • guardar el resultado de nuestra visualización en una variable, que llamaremos -por elegir algo- “p”
  • pasar la variable que contiene la visualización a la función ggplotly, que la convertirá en una versión interactiva.

Y eso es todo. A intentarlo:

p <- ggplot(filter(gapminder, año == 2007),
              aes(x = PBIpc, y = expVida, size = round(pobl/1000000,2), color = continente)) +
  geom_point() +
  scale_x_log10() +
  scale_colour_manual(values = color_continentes) + 
  guides(size = "none") +
  theme_minimal() +
  labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
       size = "población (millones)", 
       x = "PBI per capita (USD)", y = "expectativa de vida en años",
       caption = "fuente: Gapminder, www.gapminder.com")


ggplotly(p)

Si todo salió bien, debería aparecer una visualización en pantalla muy, muy similar al que hicimos con ggplot2… hasta que pasamos el puntero del mouse por encima. Ahí se hace evidente que esta versión es interactiva y permite, entre otras cosas:

  • obtener un recuadro emergente (o tooltip en la jerga de interfaces gráficas) al deslizar el puntero del mouse sobre un punto, obteniendo el valor exacto que toman las variables visualizadas
  • “arrastrar y soltar” con el mouse para definir un área sobre la cual hacer “zoom”
  • cliquear en las categorías de la leyenda para “prender” o “apagar” los datos correspondientes
  • seleccionar un subconjunto de los datos para resaltar (con los íconos del rectángulo y del lazo)
  • guardar la visualización como imagen, incluyendo los ajustes que realizamos (con el ícono de la cámara)
  • y unas cuantas opciones más a descubrir cliqueando aquí y allá

Por defecto, la “tooltip” muestra las variables que representan los atributos estéticos que asginamos al crear el ggplot, dentro de alguna llamada a aes() (“x”, “y”, “color”, etc). para controlar cuales aparecen, podemos usar el parámetro “tooltip”. Para que sólo se muestren PBI y expectativa de vida, que hemos asignado a \(x\) e \(y\), usaríamos ggplotly(p, tooltip = c("x", "y")). Algo que suele ser útil es usar la tooltip para mostrar en el recuadro valores que no hemos representado visualmente, y así poder incluir información. Podríamos querer que se muestre el país que representa cada punto ya que es práctico poder indicar el país sólo para el punto que se elige, en lugar de llenar la pantalla con etiquetas mostrando los nombres de todos los países a la vez. Para eso “inventamos” un nombre de atributo estético, por ejemplo “para_plotly” y le asignamos la variable que se verá en la tooltip. ggplot() ignora los atributos estéticos que no conoce (no hace nada con ellos), pero plotly() los recibe y puede mostrar su valores en el recuadro emergente.

Como siempre, un ejemplo va a hacerlos más claro:

Asignamos la variable que queremos mostrar en la tooltip a un atributo estético ad-hoc, como “para_plotly”:

p <- ggplot(filter(gapminder, año == 2007),
              aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente, para_plotly = pais)) +
  geom_point() +
  scale_x_log10() +
  scale_colour_manual(values = color_continentes) + 
  guides(size = "none") +
  theme_minimal() +
  labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
       size = "población (millones)", 
       x = "PBI per capita (USD)", y = "expectativa de vida en años",
       caption = "fuente: Gapminder, www.gapminder.com")

Y pedimos a plotly que use el atributo para la tooltip:

ggplotly(p, tooltip = c("para_plotly")) 

Y del mismo modo podemos obtener versiones interactivas de los otros tipos de gráficos que sabemos hacer: de barras, boxplots, histogramas, de densidad… y cualquier otro que pueda realizarse con ggplot().

Mapas interactivos

Con la explosión de de popularidad de los mapas online, con Google Maps al frente, se ha vuelto habitual explorar información geográfica en entornos interactivos, que permiten al usuario desplazarse libremente por la superficie terrestre y cambiar el nivel de zoom con el que se muestran los datos. Mapas con información tan precisa como la posición de los delitos, que incluso permite ver a parcela donde han ocurrido, se beneficia en extremo de la posibilidad de variar la escala de visualización a voluntad.

Mapas interactivos con leaflet

Desde R es fácil proyectar nuestros datos sobre mapas interactivos, usando el paquete leaflet.

Lo activamos con:

library(leaflet)

Para practicar, trabajaremos con un dataset publicado por la Ciudad Autónoma de Buenos Aires, con delitos registrados durante el 2020. Los datos fueron publicados por en el Mapa del delito de la Ciudad (https://mapa.seguridadciudad.gob.ar/). Este incluye información acerca de los distintos tipos de delito, y las coordenadas donde sucedieron.

head(delitos)
       fecha franja                  tipo subtipo cantidad   latitud  longitud
1 2020-01-01      0  Robo (con violencia)               NA -34.60489 -58.36443
2 2020-01-01      0  Robo (con violencia)               NA -34.59555 -58.37854
3 2020-01-01      0 Hurto (sin violencia)               NA -34.59437 -58.39011
4 2020-01-01      0  Robo (con violencia)               NA -34.60093 -58.39629
5 2020-01-01      0 Hurto (sin violencia)               NA -34.61671 -58.40206
6 2020-01-01      0 Hurto (sin violencia)               NA -34.61365 -58.40643
         barrio comuna
1 Puerto Madero      1
2        Retiro      1
3      Recoleta      2
4     Balvanera      3
5     Balvanera      3
6     Balvanera      3

Los más de 70.000 registros del dataset podrían resultar “pesados” para un mapa interactivo si no disponemos de bastante memoria RAM, así que practicaremos con una parte del total: los siniestros viales ocurridos en diciembre.

siniestros_viales <- delitos %>% 
  filter(subtipo == "Siniestro Vial", substr(fecha, 1, 7) == "2020-12")

El uso de leaflet es similar al de ggplot; uno toma un dataframe y lo muestra mediante capas que exponen distintos aspectos de la información. Para comenzar, usemos leaflet(siniestros_viales)

leaflet(siniestros_viales)

… y no obtuvimos mucho. Tal como pasa con ggplot(), si uno no define ninguna capa de visualización, el resultado es una especie de lienzo vacío.

Siguiente paso: agregar un mapa base, agregando al código la función addTiles(). Para sumar capas a un mapa de leaflet usamos ” %>% ” en lugar del ” + ” que requiere ggplot(), pero el concepto es el mismo.

leaflet(siniestros_viales) %>% 
    addTiles() 

Ahora está un poco mejor porque nos encontramos con un mapa, pero falta que aparezcan nuestros datos. ¡Una capa más!: con addMarkers(), leaflet se encarga de buscar las columnas que contienen coordenadas, y si aparecen con nombres reconocibles en inglés (“latitude” y “longitude”, o “lat” y “lng”) las identifica automáticamente y sitúa en el mapa un pin por cada fila.

Si las coordenadas aparecen en columnas con otros nombres (así es en nuestro caso, con nombres en castellano tenemos longitud en vez de longitude), podemos cambiar los nombres a “lat” y “lng”, o dejar la data como está e indicarle a leaflet cuáles son los nombres vía parámetros. La capa a agregar sería addMarkers(lat = ~latitud, lng = ~longitud). Obsérvese que leaflet requiere usar el simbolillo “~” antepuesto a los nombres de columna; esto le indica que debe buscar esos nombres dentro del dataframe declarado dentro de la llamada inicial a leaflet().

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addMarkers(lat = ~latitud, lng = ~longitud)

¡Ya tenemos un mapa reconocible! Para mejorarlo, agregamos el parámetro “popup”, que permite mostrar información adicional ciando se cliquea sobre un pin. Por ejemplo, el barrio, contenido en la columna “barrio”):

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addMarkers(lat = ~latitud, lng = ~longitud, popup = ~barrio)

Si en vez de “pines” preferimos señalar las posiciones con puntos usamos addCircleMarkers() en lugar de addMarkers():

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~barrio)

Una ventaja de usar círculos es que podemos cambiarles el color, y así aplicar una escala de colores para visualizar una variable a elegir. Para codificar por color, leaflet requiere definir una paleta de colores para aplicar a nuestros datos. A diferencia de ggplot() que crea la paleta de forma automática, con leaflet debemos crear una por nuestra cuenta. Por suerte contamos con funciones auxiliares que nos permiten crear paletas a medida, dependiendo del tipo de datos que vamos a mostrar: colorFactor() para variables categóricas, colorNumeric() para variables numéricas, o colorQuantile() también para variables numéricas, pero agrupadas en cuantiles. Cualquiera de las opciones requiere al menos dos parámetros. Uno es “palette”, para definir los tonos a usar (aquí funcionan nuestros amigos viridis, magma, plasma e inferno, y también las paletas Brewer, como Set1 , Spectral o Accent). El parámetro restante es “domain”, que simplemente toma un vector con los datos que vamos a representar con la paleta.

Digamos que nos interesa mostrar la variable “tipo”, que es categórica: distingue entre siniestros que causaron muertes y aquellos que sólo provocaron lesiones. Para crearle una paleta, dado que se trata de una variable categórica, debemos usar:

paleta <- colorFactor(palette = "Set1", domain = siniestros_viales$tipo)

Y luego usamos la paleta ad-hoc en nuestro mapa, asignando paleta(tipo) al parámetro “color” :

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~tipo, 
                     color = ~paleta(tipo))

También podemos mostrar valores numérico usando el tamaño de los círculos, haciendo que varíen en tamaño para mostrar distintas cantidades. Aquí hay un ligero problema, y es que leaflet es literal con el tamaño: si en una fila la variable a mostrar toma el valor “10”, su círculo se dibujará en pantalla con unos 10 píxeles de radio. Si el valor es “10.000”, los círculos tendrán ese radio aproximado en píxeles y ya ni siquiera entrarán en una pantalla normal. Por eso, al igual que con los colores, suele ser necesario crear alguna escala ad-hoc. En nuestro caso, la única columna que indica cantidades es… “cantidad”, que registra el número de personas fallecidas en cada incidente. Los única valores que toma son “NA” (sin fallecidos), 1, ó 2. Para una puesta en pantalla razonable, podemos asignar al parámetro radius el resultado de cantidad * 10:

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~paste("víctimas fatales:", as.character(cantidad)), 
                     color = ~paleta(tipo), radius = ~cantidad * 10)

Los siniestros viales sin víctimas fatales, al tener vacía la columna “cantidad” aparecen de tamaño ínfimo, casi invisibles. Prueben jugar con el valor de “radius”, asignado por ejempo cantidad (sin multiplicar) para ver que pasa.

También es útil agregar una leyenda que explique la codificación de los datos. leaflet sólo permite mostrar leyendas basadas en color (no en el tamaño de los círculos), pero algo es algo. Agregamos la leyenda con addLegend(), especificando su título, la columna que contiene los valores, y la paleta que los representa:

leaflet(siniestros_viales) %>% 
    addTiles() %>%
    addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~paste("víctimas fatales:", as.character(cantidad)),
                     color = ~paleta(tipo), radius = ~cantidad * 10) %>% 
    addLegend(title = "Tipo de siniestro vial", pal = paleta, values = ~tipo)

Por último, no podemos dejar de mencionar que leaflet trabaja muy bien con dataframes espaciales, los que contienen geometrías georreferenciadas y nos permiten visualizar líneas y polígonos además de puntos.

Para demostrarlo, traemos los radios censales de la ciuda de Buenos Aires con los que ya hemos practicado:

library(sf)
radios <- read_sf("https://bitsandbricks.github.io/data/CABA_rc.geojson")
head(radios)
Simple feature collection with 6 features and 8 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -58.38593 ymin: -34.60846 xmax: -58.35054 ymax: -34.57865
Geodetic CRS:  WGS 84
# A tibble: 6 × 9
  RADIO_ID BARRIO      COMUNA POBLACION VIVIENDAS HOGARES HOGARES_NBI AREA_KM2
  <chr>    <chr>       <chr>      <dbl>     <dbl>   <dbl>       <dbl>    <dbl>
1 1_1_1    RETIRO      1            336        82      65          19   1.80  
2 1_12_1   SAN NICOLAS 1            341       365     116          25   0.0186
3 1_12_10  SAN NICOLAS 1            296       629     101           1   0.0444
4 1_12_11  SAN NICOLAS 1            528       375     136           7   0.366 
5 1_12_2   SAN NICOLAS 1            229       445     129          16   0.0184
6 1_12_3   SAN NICOLAS 1            723       744     314         104   0.0367
# ℹ 1 more variable: geometry <MULTIPOLYGON [°]>

La idea es repetir el mapa de densidad poblacional que hicimos con ggplot() en la clase 4. La variable a mostrar vuelve a ser la relación POBLACION/AREA_KM2.

La primer buena noticia es que ahora no hace falta aclarar cuales son las columnas de latitud y de longitud. En un dataframe espacial la información de coordenadas siempre está contenida por la columna geometry, y leaflet sabe cómo interpretarla. Por lo tanto, al trabajar con este formato no tenemos que especificar los parámetros lat y lng.

La segunda buena noticia es que lo que practicamos con el dataset de delitos se aplica en gran medida a este caso. El cambio importante es que ahora usaremos addPolygons() en vez de addCircles() (porque estamos mostrando geometrías poligonales). Pero lo demas es similar.

De nuevo necesitamos crear una una paleta, esta vez usando colorNumeric() ya que lidiamos con una variable numérica:

paleta2 <- colorNumeric(palette = "viridis", domain = radios$POBLACION / radios$AREA_KM2)

Y la llevamos al mapa de forma análoga a como hicimos para mostrar los siniestros viales, pero esta vez al parámetro “fillColor” (¡el color de relleno!) le asignamos paleta2(POBLACION/AREA_KM2):

leaflet(radios) %>% 
    addTiles() %>%
    addPolygons(popup = ~paste("barrio:", BARRIO, "</br>población:", as.character(POBLACION)), 
                fillColor = ~paleta2(POBLACION/AREA_KM2))

El resultado se ve muy poco legible, pero paciencia. De forma similar a cuando pulimos el mapa de densidad realizado con ggplot(), aquí tendremos que ajustar unos parámetros para lograr buenos resultados. A saber:

  • Usar un mapa base de tonos neutros, para que resalten los colores de nuestra visualización vía addProviderTiles() (véase https://rstudio.github.io/leaflet/basemaps.html para más opciones)
  • Eliminar la línea del perímetro de los polígonos, así impidiendo que tape el color de relleno, que es lo que nos interesa, vía weight = 0
  • Subir la opacidad del color de relleno, para que se distinga mejor, con fillOpacity = 0.9
leaflet(radios) %>% 
    addProviderTiles(providers$Stamen.TonerLite) %>%
    addPolygons(popup = ~paste("barrio:", BARRIO, "</br>población:", as.character(POBLACION)), 
                fillColor = ~paleta2(POBLACION/AREA_KM2),
                weight = 0,
                fillOpacity = 0.9)

¡Ahí está mejor!

Esto ha sido sólo una introducción a la producción de mapas interactivos. Para acceder a un recorrido por muchas otras opciones disponibles con leaflet, podemos visitar https://rstudio.github.io/leaflet/

Animando las cosas

La animación es un recurso a veces complicado de implementar, pero muy poderoso para hacer que las datos “cuenten una historia”. Es sin duda una forma poderosa de comunicar, por su capacidad de llamar la atención de la audiencia y con ello lograr que nos dediquen los siguientes momentos de su día.

Si bien las herramientas para animar gráficos corresponden por tradición al mundo del arte más que del análisis de datos, recientemente ha aparecido una herramienta que brinda a R un marco de trabajo para crear visualizaciones animadas. Se trata de gganimate, un paquete que extiende la funcionalidad de ggplot2 y nos permite tomar como base las visualizaciones que ya sabemos hacer, aplicando un conjunto de funciones especializadas para obtener versiones animadas.

Es imposible mostrar en toda su variedad las formas en que podemos realizar animaciones, pero vamos a mostrar como emular dos estilos bastante conocidos: la carrera de gráfico de barras (que ya es una especie de género en si mismo, con sus detractores y´) y la animación de Gapminder, que vimos en la producción de la BBC para la primera clase de este curso.

Una carrera de barras

Continuamos con nuestra querida data de Gapminder. ¿Qué tal si mostramos el incremento de la población a través de los años? Lo haremos con una “carrera” entre países, mostrando los que alcanzan mayor cantidad de habitantes en cada año registrado.

Continuando con la estrategia establecida en este curso, aprovecharemos lo que ya sabemos hacer con ggplot2, y construiremos sobre eso.

El primer ingrediente a resolver para la animación que tenemos en mente es un gráfico de barras, que realizaremos con ggplot(). Aquí tenemos que tomar algunas decisiones de diseño:

  • Para cada año mostraremos la población alcanzada para los puestos 1 al 10 del ranking de población mundial (tendremos que preparar los datos y crear una columna “ranking”)
  • Para mejor legibilidad, hagamos que las barras se dibujen en forma horizontal (esto ya lo hemos practicado, y es consistente con la forma en la que suelen mostrarse las bar chart races)
  • Usemos el color de relleno de las barras para mostrar el continente de cada país
  • Agreguemos una etiqueta al final de cada barra para identificar el país.

Primer paso, transformar los datos. Para obtener una versión de los datos que incluya sólo los 10 países con mayor población para cada año, podemos usar las funciones de dplyr:

gapminder_ranking <- gapminder %>% 
  group_by(año) %>% # agrupa los datos según el valor de la columna "año"
  arrange(año, desc(pobl)) %>% # ordena los miembros de cada grupo por población, de mayor a menor 
  mutate(ranking = row_number()) %>% # crea una columna numérica con el ranking
  filter(ranking <= 10) # retiene solo el "top ten" y descarta las demás filas 

Nos queda así:

gapminder_ranking
# A tibble: 120 × 7
# Groups:   año [12]
   pais           continente   año expVida      pobl  PBIpc ranking
   <chr>          <chr>      <int>   <dbl>     <int>  <dbl>   <int>
 1 China          Asia        1952    44   556263527   400.       1
 2 India          Asia        1952    37.4 372000000   547.       2
 3 United States  Americas    1952    68.4 157553000 13990.       3
 4 Japan          Asia        1952    63.0  86459025  3217.       4
 5 Indonesia      Asia        1952    37.5  82052000   750.       5
 6 Germany        Europe      1952    67.5  69145952  7144.       6
 7 Brazil         Americas    1952    50.9  56602560  2109.       7
 8 United Kingdom Europe      1952    69.2  50430000  9980.       8
 9 Italy          Europe      1952    65.9  47666000  4931.       9
10 Bangladesh     Asia        1952    37.5  46886859   684.      10
# ℹ 110 more rows

Ahora, a preparar el gráfico. Como insumo para la animación, necesitamos una visualización realizada con ggplot(). Hay infinidad de maneras de abordar el desafío; tantas como formas de mostrar poblaciones, países y año. En esta ocasión haremos un “facetado” mostrando el ranking de población con facetas para cada año en el dataset:

ggplot(gapminder_ranking) +
  geom_col(aes(x = pobl / 1000000, y = factor(ranking), fill = continente)) +
  geom_text(aes(x = pobl / 1000000, y = factor(ranking), label = pais)) +
  scale_fill_manual(values = color_continentes) +
  theme_minimal() +
  facet_wrap(vars(año)) +
  labs(y = NULL, x = "población (millones)")

Una vez que quedó como lo queremos, la animación se resuelve reemplazando el facet_wrap() por una de las funciones de gganimate que animan transiciones. Hay varias para elegir, por ejemplo:

  • transition_states(): Anima transiciones entre “estados” diferentes (por ejemplo, de “en proceso” a “despachado”)
  • transition_time(): Anima transiciones entre momentos en el tiempo
  • transition_layers(): Anima un gráfico en “capas”, de modo que vayan apareciendo en forma gradual

Y varias más. Son difíciles de describir, así que no hay mejor modo de entender la diferencia entre los métodos de animación que probarlos. Un buen punto para empezar es el tutorial de “primeros pasos” en el sitio oficial del paquete.

Con la data que tenemos entre manos, queda claro que vamos a usar transition_states(), con la variable “año” dictando los distintos momentos a mostrar. Para que el título del gráfico muestre un texto distinto dependiendo del año en cada momento de la animación, usaremos un truco que aporta gganimate: Si en el texto asignado al título incluimos {frame_time}, ese indicador será reemplazado por el valor de la variable de tiempo según el punto de la animación.

Veanse las dos últimas líneas, que contienen el código nuevo, y el resultado:

gapminder_anim1 <- ggplot(gapminder_ranking, aes(group = pais)) +
  geom_col(aes(x = pobl / 1000000, y = factor(ranking), fill = continente)) +
  geom_text(aes(x = pobl / 1000000, y = factor(ranking), label = pais)) +
  scale_fill_manual(values = color_continentes) +
  theme_minimal() +
  transition_time(año) +
  labs(title = "Año: {frame_time}", y = NULL, x = "población (millones)")

gapminder_anim1

install.packages("gifski")

Por último, un detalle más: vean que en la primera línea agregamos aes(group = pais). Esto sirve para que gganimate entienda que los valores de la esa variable representan a la misma entidad a lo largo de los años, haciendo que cuando un pais gana o pierde puestos esto se refleje en un desplazamiento de su barra. Para ver lo que pasaría si no agregáramos aes(group = pais), ejecuten el código borrando esa parte y revisen los resultados.

Un mundo en convergencia

Vamos a terminar con nuestra variante de la visualización que Has Rosling llamaba “un mundo en convergencia”: el gradual cierre de la enorme brecha que separaba a las principales naciones occidentales industrializadas del resto del mundo.

Recuperemos una vez más nuestro gráfico gapmindereano:

ggplot(filter(gapminder, año == 2007),
       aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
  geom_point() +
  scale_x_log10() +
  scale_colour_manual(values = color_continentes) + 
  guides(size = "none") +
  theme_minimal() +
  labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
       size = "población (millones)", 
       x = "PBI per capita (USD)", y = "expectativa de vida en años",
       caption = "fuente: Gapminder, www.gapminder.com")

Bien, aplicando los mismos ajustes que realizamos para animar la carrera de barras, podemos obtener la versión dinámica. Esta vamos a usar todo el dataset, ya que nos interesa tener las mediciones en distintos años. O sea, en lugar del dataset modificado con filter(gapminder, año == 2007) lo vamos a usar completo, sin aplicarle filtros.

ggplot(gapminder,
       aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
  geom_point() +
  scale_x_log10() +
  scale_colour_manual(values = color_continentes) + 
  guides(size = "none") +
  theme_minimal() +
  labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos {frame_time}",
       size = "población (millones)", 
       x = "PBI per capita (USD)", y = "expectativa de vida en años",
       caption = "fuente: Gapminder, www.gapminder.com") +
  transition_time(año)

Y con eso terminamos esta breve introducción a las visualizaciones animadas. Para continuar explorando las muchísimas opciones disponibles con gganimate() el mejor mejor recurso -al momento de escribir estas líneas- es el sitio oficial del paquete.

Para continuar aprendiendo sobre visualizaciones interactivas, se puede acceder en forma gratuita al contenido del libro Interactive web-based data visualization with R, plotly, and shiny” de Carson Sievert.

¡Hasta la próxima!